查看原文
其他

中文NER任务实验小结:BERT-MRC的再优化

邱震宇 PaperWeekly 2022-07-04


©作者 | 邱震宇

单位 | 华泰证券算法工程师

研究方向 | NLP方向



前言

熟悉我的读者,应该看过我之前写过的一篇关于中文 NER 任务实践的文章(邱震宇:中文 NER 任务实验小结报告——深入模型实现细节 [1],在那篇文章中,我介绍了一个做 NER 任务的新范式:将实体抽取任务转化为抽取式阅读理解任务。

其核心思想就是将待抽取的实体标签描述作为 query 与原始的文本进行拼接,然后基于 BERT 做对应 span 的抽取。通过这种方式能够让模型学习到实体标签本身的语义信息,引入先验知识,从而提升模型的能力。在我之后的实现中,也进一步验证了这个方法的有效性。但我尝试将这个方法应用到实际场景任务时,却遇到了很多限制。

其中,影响最大的就是在线预测的效率。在实际场景中,我们的实体或者其他序列标注要素的标签类型通常会有很多(假定有 |C| 个),这意味着我们要将每个待预测的文本与 |C| 个标签 query 模板进行拼接得到模型输入,并需要调用 |C| 次模型的前向计算才能完成一个样本的抽取。假设 query 的最长文本长度为 n,原始文本的文本长度为 m,由于进行了 self-attention 的计算,整个计算的复杂度为 ,这样的在线预测效率是无法满足线上的需求的。
针对这个问题,我尝试了一些优化方法,但是都不太理想,最近终于在一篇  EMNLP2021 的论文中找到了比较好的方案,论文如下:Enhanced Language Representation with Label Knowledge forSpan Extraction [2]。这篇论文提出了一个 LEAR(Label knowledge EnhAnced Representation)模型架构,尝试优化 BERT-MRC 的一些缺陷,下面我将详细给大家描述这个方法,并对其进行有效性的验证。
本文涉及源码:
https://github.com/qiufengyuyi/lear_ner_extraction


LEAR原理

2.1 BERT-MRC的缺陷

首先,论文提到了之前使用 BERT-MRC 的方式做序列标注问题,虽然相比传统的 BERT-CRF 方法有一定的效果提升,但是仍然有两个缺陷。其中一个就是前言中提到的效率问题。另外一个则是 BERT-MRC 并没有充分利用标签的知识信息。前言中提到,BERT-MRC 引入了标签的先验知识,然而 LEAR 论文中通过对 attention 部分进行可视化分析,发现模型可能没怎么用到 query 的信息。如图 1 所示,实体 judge 对应的高分 attention 并非如预期一样集中在问题的核心部分,而是分散在了一些无关的信息中(如 [CLS] 等 token)。

▲ 图1 attention 可视化

通过上述分析,可以发现 BERT-MRC 对于先验知识的利用率并不如预期。因此,LEAR 设计了一种新的标签知识整合方式,让模型高效利用标签先验知识,同时解决 BERT-MRC 的效率和知识利用率问题。

2.2 LEAR模型架构

LEAR 的模型架构如图 2 所示。

▲ 图2 LEAR 模型

其中,模型的输入分为两部分:原始的待抽取文本以及所有标签对应的描述文本(先验知识)。与 BERT-MRC 不同,我们不会将标签描述文本与原始文本进行拼接,而是使用 BERT 的编码器分别进行编码得到不同的文本表示。
值得注意的是,为了提升训练效率和减小模型的尺寸,原始文本和标签描述文本共享 BERT 的编码器权重;之后我们引入一个标签知识融合模块,将所有标签描述文本的表示融合到原始文本中;最后我们使用引入分类器来识别待抽取 span 的 start 和 end 位置的概率分布,并使用一些 decoding 策略来抽取实体。下面,我将详细描述融合模块和分类模块的具体内容。
标签信息融合模块
假设我们通过 BERT 编码器得到了原始文本的表示 以及所有标签的描述文本表示 ,其中 n 表示原始文本的长度,m 为标签文本的统一长度(经过 padding 之后),|C| 表示标签类别数量,d 表示 hidden_size。标签信息融合模块本质上是一个注意力机制模块,其目的就是要让模型学习到原始文本中的各个 token 会关注到标签描述文本中的哪些内容。
对每个标签 c,我们将 视作 attention 计算中的 key 和 value 元素,将 作为 attention 中的 query 元素(关于 attention 中的Q、K、V 可以参考我以前的 attention 文章介绍 Attention 机制简单总结 [3],进行点乘模式的 attention 计算并得到 attention 分数。在做点乘计算之前,我们参考 attention 典型的做法分别对原始文本表示和标签描述文本表示进行线性映射:

然后我们对其进行 attention 计算:

接下来就是对 value 元素应用所得到的 attention 分数进行加权求和操作,得到原始文本某个 token  的 context 信息(这里在实现时要使用标签描述文本的 mask 信息,把 padding 位置的值 mask 掉):

我们接下来要做的就是将 context 信息融合到原始文本向量表示中。论文使用的方式是直接用 ADD 操作,将 与 context 相加:


我尝试过 concat 拼接融合,最后发现效果并不如直接 add。在此架构下,add 操作的融合有更高的信息利用率。

之后,我们引入一个 dense layer,将最终的文本表达输出,其中激活函数使用了 tanh,相比 sigmoid,其值域更广,而且防止向量表示的绝对值过大:

最后,我们对每个文本 token、标签类别都进行上述流程,最终得到完整的融合向量表示:
在模型实现时,所有标签类别和所有 token 的计算可以在一次矩阵的计算中完成。

span抽取分类模块

与 BERT-MRC 类似,LEAR 最后也是预测某个 span 的 start 和 end 的位置 token。但由于 LEAR 中将所有标签类别的预测都放在的一次前向计算中,因此最后的分类层与传统的方式有所不同。对于某个原文文本的 token ,我们计算其作为某类实体的 start 概率分布:

其中, 分别是可训练的权重,而   表示 element-wise的矩阵乘法,而 表示对输入矩阵中的 hidden_size 所在维度进行求和,最终得到一个 |C| 维的向量。

虽然上面的公式看起来还是有点绕,但实现起来也不是很复杂,在 TensorFlow 的框架下,可以这么实现:

def classifier_layers(self,input_features,type="start"):
        #input features
        # batch_size,input_seq_len,num_labels,hidden_size
        # input_shape = modeling.get_shape_list(input_features)
        # batch_size = input_shape[0]
        # seq_length = input_shape[1]
        classifer_weight = tf.get_variable(
        name="classifier_weight"+"_"+type,
        shape=[self.num_labels, self.attention_size],
        initializer=modeling.create_initializer(self.bert_config.initializer_range))
        classifer_bias = tf.get_variable(name="classifier_bias"+"_"+type,shape=[self.attention_size],initializer=tf.constant_initializer(0))
        output = tf.multiply(input_features,classifer_weight)
        output += classifer_bias
        #[batch_size, input_seq_len, num_labels]
        output = tf.reduce_sum(output,axis=-1)
        return output

其中 tf.multiply 是一个 element-wise 矩阵乘法,且支持 broadcasting 机制。

2.3 span decoding
对于某类实体的 end 概率计算,与 start 的流程是一样的。最后每个样本将得到两个概率输出: 。根据这两个输出与预先设计的概率阈值(一般是 0.5),我们就能一次性得到所有标签类别的起始位置列表,但是要得到具体的 span 实体,还需要设计一些 decoding 策略。

论文中提出了两种 decoding 策略,分别为最近距离策略启发式策略。最近距离策略就是先定位所有的 start 位置,然后找距离 start 位置最近的 token 作为 end。而启发式策略则稍微复杂一些,若定位到一个 start1 位置,不会马上决定它作为一个 span 的起始位置,若后面的 token 如果也是一个候选的 start2,且其概率还要大于 start1,则会选择新的 start2 作为 start 候选。具体大家可以参考论文后面的附录,有详细的算法流程。

说实话,我感觉论文中附录的算法伪代码有些问题,对于 end 位置的判断应该不需要像 start 一样选择概率最高的。另外,我自己的训练数据预处理时,对于单字成实体的情况,我的 end 位置是置为 0 的,而论文和开源代码中的设置却不同,因此不好做直接对比。

我自己实现时,使用的是变种的最近距离策略。因为选择与 start 最近的 end 时,有可能会越过下一个新的 start 位置,这有可能是算法本身预测有问题,或者有实体嵌套的情况(目前还未考虑嵌套实体的 case),如图 3 所示。

▲ 图3 最近距离decoding策略

我这边采取了比较简单的策略,就是在 start1 和 start2 的区间中,来定位与 start1 最近的 end 位置。那么上图中的情况下,就不会选择 s1-e1 作为结果,而是只选择 s1 位置处的单个 token 作为结果输出:

▲ 图4 自定义的最近距离decoding策略

大家可以自行尝试不同的 decoding 策略,我感觉每种策略的适用场景都不一样,需要根据实际情况来判断。
2.4 LEAR的效率
LEAR 相对于 BERT-MRC 的最大优势在于其较高的 inference 效率。由于 LEAR 不需要为每个原始文本构造 |C| 个新样本,理论上 LEAR 的计算复杂度为 。可以看到,这个复杂度是远小于 BERT-MRC 的,在后面的验证中,我也得到了预期的效果。


LEAR实现

我最近分享的一些方法大部分都经过实验并验证有效的,这次介绍的 LEAR 也不例外。论文有放出 pytorch 的源码:https://github.com/ Akeepers/LEAR。我照例还是使用 tensorflow 来实现,最近重新看了下之前的 NER 框架代码,感觉有很多地方写的不太合理,因此我又重新开了个 repo。LEAR 的实现也不是太复杂,需要注意的地方主要有以下几个方面:
1. 构造模型输入时,要专门针对实体标签的描述文本进行预处理。在做 input_fn 的时候,使用 tf.data.Dataset.from_generator 来进行 batch 数据流的构建,但是我们的标签文本本来是没有 batch 概念的,所有训练样本都只用一份标签文本,因此在 model 方法定义中,要人为将 tf 添加的 batch 维度去掉:
if label_token_ids.shape.ndims == 3:
            label_token_ids = label_token_ids[0,:,:]
            label_token_type_ids = label_token_type_ids[0,:,:]
            label_token_masks = label_token_masks[0,:,:]
            label_token_length = label_token_length[0,:]
            label_lexicons = label_lexicons[0,:,:,:]


2. 原始文本和标签文本共享 encoder 参数,这里要对 google原始的 BERT 的 modeling.py 进行修改,在模型定义的时候,添加 auto_reuse 配置,同时在调用 bert 的时候,传入 scope="bert":


with tf.variable_scope(scope, default_name="bert",reuse=tf.AUTO_REUSE):


3. 论文和官方开源的代码都没有对 attention 计算后的分数进行 scaling。我觉得可能的原因是融合模块中的 attention 操作只有一层, 且注意力的分数并没有直接用于词 softmax 的计算,而是融合到了原始文本的向量表示中,另外在最后输出的时候使用了 tanh 进行了激活,输出的值不会太大,因此即使不做 scale,也不会产生很严重的梯度消失问题,并影响后面的分类器的计算。我这边也验证了,加入了 scaling 之后,效果相差不多。

最后,我将 LEAR 在中文的 MSRA 的 NER 数据集上进行了验证,同时与之前已经实现过的 BERT-MRC 进行了对比。验证指标则是考虑了实体类型后的 micro-level 的 f1 分数,具体来说我会将实体类型字符串与实体内容拼接,做去重后,进行 exact match 匹配。最终,得到的结果大体如下:

可以看到,LEAR 的效果相比 BERT-MRC 来说有一定提升,但是不够明显,但是 inference 的效率却是显著提升了,这也是我最关注的地方,这意味着这个方法可以应用在实际的业务场景中!


彩蛋!

因为很长时间没写文章了,所以写一次就尽量多包含点内容。为了奖励读到这边的同学,我再奉送一段额外的 NER 优化技巧。

读过我上一篇中文 NER 总结的同学应该记得我在那篇文章中提到尝试在 BERT 中引入词汇信息,当时尝试的办法很依赖分词的质量,且融合词汇信息的方式也比较简单。因此,这次我参考了最近比较火的一篇论文来做词汇增强:Simplify the Usage of Lexicon in Chinese NER [4]

这篇论文解读我就不做了,大家可以参考这篇:JayJay:中文 NER 的正确打开方式: 词汇增强方法总结(从 Lattice LSTM 到 FLAT)[5]。这个方法的核心思想是先准备一份词向量和词表,然后对当前所有语料中的文本字符统计其分别属于 BMES 的信息,B 代表词的开头,M 代表词的中间,E 代表词的结束,S 代表单独成词。

假设对于某个 token x,若某个样本 A 中存在片段 span,其开头为 x,则将 span 对应的词向量添加到 B 对应的列表中,其他类型同样操作。最后,我们将每个类型中的词向量做加权平均,得到增加的词汇信息与原始的 token 向量表示拼接。

▲ 图5 词汇增强方法

这个方法实现的最大难点在于如何快速找到包含某个字符,且符合特定位置关系的 span 词。原论文开放了代码:
https://github.com/v-mipeng/LexiconAugmentedNER 

其使用了 trie 数来构建词典树,这也是海量字符串匹配场景中会使用的一种方法。我在实现的时候,没有用这种方式,为了快速得到效果,使用了一种比较基础的方式,就是在数据预处理的时候,将每个字符对应的 BMES 的词列表信息都预先存储起来,在模型输入的时候直接读取信息,这样做速度不慢,但是对内存的要求就比价高了,类似于空间换时间。

另外,在使用 tensorflow 实现完整的功能时,我发现坑有点多。因为静态图中,你要将每个字符的 BMES 词表序列作为输入传到模型图中,然后用 embedding_lookup 分别找到词对应的词向量,再做加权平均,而每个字符实际的 BMES 词表 size 都不一样,这意味着更多 padding 和 mask 的处理,想想都头大。。。所以我偷了个懒,不让词汇增强部分参与训练而是将其作为固定的向量表示与原始 token 向量拼接,看其是否仍有增益的效果,结果居然也有一定的增益,结合 LEAR 架构,我得到了如下的结果:


TIPS:我在构建词表的时候,为了提高速度,将出现频率小于 5 的词都过滤掉了。

可以看到增加了词汇增强信息后,LEAR 的效果有一定的提升。如果对这个方法感兴趣,可以尝试实现完整功能的增强,让增强信息也参与到模型学习中,最后应该会有很大的提升。



小结
本文主要介绍了一种优化 BERT-MRC 的方法,针对其预测效率低、没有充分利用标签信息的两个缺陷,设计了一种专门针对标签文本的注意力机制融合方法,在提升模型效果的同时,大大提升了基于 MRC 做 NER 的效率,使其能够应用在实际的业务场景中。另外,本文也额外介绍了一种不依赖于分词工具的词汇增强方法,经过验证,证明其与其他 BERT 类的方法结合能够提升模型的抽取效果。

参考文献

[1] https://zhuanlan.zhihu.com/p/103779616
[2] https://aclanthology.org/2021.emnlp-main.379.pdf
[3]  https://zhuanlan.zhihu.com/p/46313756
[4] https://aclanthology.org/2020.acl-main.528.pdf
[5] https://zhuanlan.zhihu.com/p/142615620


特别鸣谢

感谢 TCCI 天桥脑科学研究院对于 PaperWeekly 的支持。TCCI 关注大脑探知、大脑功能和大脑健康。



更多阅读




#投 稿 通 道#

 让你的文字被更多人看到 



如何才能让更多的优质内容以更短路径到达读者群体,缩短读者寻找优质内容的成本呢?答案就是:你不认识的人。


总有一些你不认识的人,知道你想知道的东西。PaperWeekly 或许可以成为一座桥梁,促使不同背景、不同方向的学者和学术灵感相互碰撞,迸发出更多的可能性。 


PaperWeekly 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是最新论文解读,也可以是学术热点剖析科研心得竞赛经验讲解等。我们的目的只有一个,让知识真正流动起来。


📝 稿件基本要求:

• 文章确系个人原创作品,未曾在公开渠道发表,如为其他平台已发表或待发表的文章,请明确标注 

• 稿件建议以 markdown 格式撰写,文中配图以附件形式发送,要求图片清晰,无版权问题

• PaperWeekly 尊重原作者署名权,并将为每篇被采纳的原创首发稿件,提供业内具有竞争力稿酬,具体依据文章阅读量和文章质量阶梯制结算


📬 投稿通道:

• 投稿邮箱:hr@paperweekly.site 

• 来稿请备注即时联系方式(微信),以便我们在稿件选用的第一时间联系作者

• 您也可以直接添加小编微信(pwbot02)快速投稿,备注:姓名-投稿


△长按添加PaperWeekly小编




🔍


现在,在「知乎」也能找到我们了

进入知乎首页搜索「PaperWeekly」

点击「关注」订阅我们的专栏吧



·

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存